package com.bjy.qa.service.mock.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bjy.qa.dao.functionaltest.ApiDao;
import com.bjy.qa.dao.manage.ProjectDao;
import com.bjy.qa.entity.functionaltest.Api;
import com.bjy.qa.entity.functionaltest.ApiParam;
import com.bjy.qa.entity.manage.Project;
import com.bjy.qa.entity.mock.ConditionsDTO;
import com.bjy.qa.entity.mock.MockDTO;
import com.bjy.qa.entity.mock.http.MockContentType;
import com.bjy.qa.entity.mock.http.MockForward;
import com.bjy.qa.entity.mock.http.MockRequest;
import com.bjy.qa.entity.mock.http.MockResponse;
import com.bjy.qa.enumtype.Comparison;
import com.bjy.qa.enumtype.ForwardMode;
import com.bjy.qa.enumtype.ParamKind;
import com.bjy.qa.service.mock.IMockService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@org.springframework.stereotype.Service
public class MockService implements IMockService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    ProjectDao projectDao;

    @Resource
    ApiDao apiDao;

    @Override
    public MockResponse getMockResponse(MockRequest mockRequest) {
        MockResponse mockResponse = new MockResponse();
        try {
            Map<String, String> uri = splitUrl(mockRequest.getUri()); // 拆分 uri（拆成 preUri：前置 uri， realUri：真实 uri（去掉 /mock/abc 后的 uri））

            // 查询 前置 url 对应的项目信息
            QueryWrapper<Project> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("mock", true);
            queryWrapper.eq("mock_pre_url", uri.get("preUri"));
            Project project = projectDao.selectOne(queryWrapper);
            if (project == null) {
                mockResponse.setStatus(404);
                mockResponse.setContentType(MockContentType.JSON);
                mockResponse.setBody("{\"status\": 404,\"error\": \"未找到 mock 中 前置 url 对应的项目或该项目未开启 mock\",\"path\": \"" + mockRequest.getUri() + "\"}");
                return mockResponse;
            }

            List<Api> apiList = apiDao.selectByUri(project.getId(), mockRequest.getMethod().getName(), uri.get("realUri")); // 根据前置 url 和真实 url 查询 api 信息

            // 遍历 URI 中带参数的 api 信息，看下使用正则表达式是否能匹配到 request 中的 uri
            List<Api> uriParamApiList = apiDao.selectByUriParam(project.getId(), mockRequest.getMethod().getName()); // 查询 URI 中带参数的 api 信息
            for (Api tmpApi : uriParamApiList) {
                if (StringUtils.isNotBlank(tmpApi.getUri())) {
                    String requestUriPattern = tmpApi.getUri().replaceAll("\\$\\{.*?\\}", "[^/]*?"); // 将 api 定义中的 uri 中的参数，替换成正则表达式
                    if (!requestUriPattern.equals("(?![^/]).*?") && Pattern.matches(requestUriPattern, uri.get("realUri"))) {
                        apiList.add(tmpApi);
                    }
                }
            }

            if (apiList.size() == 0) { // 没匹配到 uri，返回 404
                if (project.getForwardMode().getValue() >= ForwardMode.API_NOT_DEFINE.getValue()) { // 项目配置了转发，返回 agent 转发地址
                    MockForward mockForward = new MockForward();
                    mockForward.setEnable(true);
                    mockForward.setHost(project.getForwardUrl());
                    mockForward.setUri(uri.get("realUri"));
                    mockResponse.setMockForward(mockForward);
                    return mockResponse;
                } else { // 否则返回 404
                    mockResponse.setStatus(404);
                    mockResponse.setContentType(MockContentType.JSON);
                    mockResponse.setBody("{\"status\": 404,\"error\": \"未找到 mock 地址或未开启 mock\",\"path\": \"" + mockRequest.getUri() + "\"}");
                    return mockResponse;
                }
            } else if (apiList.size() > 1) { // 匹配到多个 uri，返回 422 错误
                mockResponse.setStatus(422);
                mockResponse.setContentType(MockContentType.JSON);
                mockResponse.setBody("{\"status\": 422,\"error\": \"当前项目下存在多个接口拥有相同路径: " + mockRequest.getUri() + " 产生冲突，请保证当前项目接口路径唯一\",\"path\": \"" + mockRequest.getUri() + "\"}");
                return mockResponse;
            } else { // 匹配到 uri，继续匹配 mock
                Api api = apiDao.selectById(apiList.get(0).getType().getValue(), apiList.get(0).getId()); // 根据查到的 api 信息，查询 api 详细信息

                // 遍历，拿到 api 定义的 mock 列表
                List<MockDTO> mockList = null;
                for (ApiParam apiParam : api.getApiParams()) {
                    if (apiParam.getKind() == ParamKind.KIND_MOCK) {
                        mockList = JSON.parseArray(apiParam.getDes(), MockDTO.class);
                    }
                }

                boolean isMatch = false; // 是否匹配到 mock
                if (mockList != null) {
                    for (MockDTO mock : mockList) { // 遍历 mock 列表
                        boolean isMatchConditions = true; // 是否匹配到 mock 的期望条件
                        for (ConditionsDTO conditions : mock.getConditions()) { // 遍历 mock 的期望条件
                            String requestValue = this.getRequestValue(conditions, mockRequest, api); // 根据期望的 key 从请求中得到参数值
                            String conditionsValue = conditions.getValue(); // get 期望中的参数值

                            // 比较（期望中的 value 和请求中的 value 比较）
                            isMatchConditions = this.comparison(conditions, requestValue, conditionsValue);
                            if (!isMatchConditions) { // 如果期望中的 value 和请求中的 value 不匹配，直接匹配失败，跳出循环
                                break;
                            }
                        }
                        if (isMatchConditions) {
                            isMatch = true;
                            mockResponse.setBody(mock.getResponseBody());
                            mockResponse.setStatus(Integer.valueOf(mock.getResponseCode()));
                            mockResponse.setContentType(mock.getResponseType());
                            break;
                        }
                    }
                }

////                Map<String, String> headers = new HashMap<>();
////                headers.put("head_key", "head_value");
////                mockResponse.setHeaders(headers);
////
////                Map<String, String> cookies = new HashMap<>();
////                cookies.put("cookies_key", "cookies_value");
////                mockResponse.setCookies(headers);

                // 没有查询到 mock 配置，直接返回 api 定义的 assert
                if (!isMatch) {
                    if (project.getForwardMode() == ForwardMode.CONDITIONS_NOT_DEFINE) { // 项目配置了定义了 url 但没有匹配上条件转发，返回 agent 转发地址
                        MockForward mockForward = new MockForward();
                        mockForward.setEnable(true);
                        mockForward.setHost(project.getForwardUrl());
                        mockForward.setUri(uri.get("realUri"));
                        mockResponse.setMockForward(mockForward);
                        return mockResponse;
                    } else { // 否则返回 api 定义的 assert
                        mockResponse.setStatus(200);
                        mockResponse.setContentType(MockContentType.JSON);

                        for (ApiParam apiParam : api.getApiParams()) {
                            if (apiParam.getKind() == ParamKind.KIND_ASSERT) {
                                mockResponse.setBody(apiParam.getExample());
                            }
                        }
                    }
                }
            }
        } catch (Exception e) { // 捕捉所有异常，返回 400 给 mock 调用端
            mockResponse.setStatus(400);
            mockResponse.setContentType(MockContentType.JSON);
            mockResponse.setBody("{\"status\": 400,\"error\": \"" + e.getMessage() + "\",\"path\": \"" + mockRequest.getUri() + "\"}");
        }

        return mockResponse;
    }

    /**
     * 拆分 uri（拆成 preUri：前置 uri， realUri：真实 uri（去掉 /mock/abc 后的 uri））
     * @param uri mock uri
     * @return Map<String, String> preUri：前置 uri， realUri：真实 uri（去掉 /mock/abc 后的 uri
     */
    private Map<String, String> splitUrl(String uri) {
        Map<String, String> map = new HashMap<>();

        if (StringUtils.isBlank(uri)) {
            throw new RuntimeException("mock uri 不能为空");
        } else if (uri.indexOf("/mock/") != 0) {
            throw new RuntimeException("mock uri 必须以 /mock/ 开头");
        }

        String[] splitUri = uri.substring(6).split("/");
        if (splitUri.length < 1 || StringUtils.isBlank(splitUri[0])) {
            throw new RuntimeException("mock uri 格式错误，没有找到 前置 url。如果你定义的前置 url 是 abc 那你请求的 mock 地址应类似 http://192.168.1.1:6625/mock/abc/user/login");
        }

        map.put("preUri", splitUri[0]); // 前置 uri

        // 真实 uri（去掉 /mock/abc 后的 uri）
        String realUri = uri.substring(6 + splitUri[0].length());
        if (StringUtils.isBlank(realUri)) {
            realUri = "/";
        }
        map.put("realUri", realUri);

        return map;
    }

    /**
     * 根据期望的 key 从请求中得到参数值
     * @param conditions 期望
     * @param mockRequest mock 请求
     * @param api api 信息
     * @return 请求中的参数值
     */
    private String getRequestValue(ConditionsDTO conditions, MockRequest mockRequest, Api api) {
        String requestValue = null;
        switch (conditions.getLocation()) {
            case QUERY:
                requestValue = mockRequest.getParamsValue2String(conditions.getName()); // 根据参数名，从请求 url 中拿到参数值
                break;
            case PATH:
                String requestUri = splitUrl(mockRequest.getUri()).get("realUri"); // 拆分 uri（拆成 preUri：前置 uri， realUri：真实 uri（去掉 /mock/abc 后的 uri））

                // 将 api uri 中的 ${param} 替换成正则表达式
                String paramPatternStr = api.getUri().replaceAll("\\$\\{" + conditions.getName() + "\\}", "([^/]*)");
                Pattern paramPattern = Pattern.compile(paramPatternStr);

                // 从请求 uri 中，根据正则表达式，拿到参数值
                Matcher matcher = paramPattern.matcher(requestUri);
                if (matcher.find()) {
                    if (matcher.groupCount() >= 1) {
                        requestValue = matcher.group(1); // 获取 secret_key
                    }
                }
                break;
            case HEADER:
                requestValue = mockRequest.getHeaders(conditions.getName()); // 根据参数名，从请求 headers 中拿到参数值
                break;
            case COOKIE:
                requestValue = mockRequest.getCookies(conditions.getName()); // 根据参数名，从请求 cookies 中拿到参数值
                break;
            case BODY:
                requestValue = mockRequest.getBodyValue2String(conditions.getName()); // 根据参数名，从请求 body 中拿到参数值
                break;
            default:
                throw new UnsupportedOperationException("错误的 location : " + conditions.getComparison());
        }
        return requestValue;
    }

    /**
     * 比较（期望中的 value 和请求中的 value 比较）
     * @param conditions 期望
     * @param requestValue 请求中的参数值
     * @param conditionsValue 期望中的参数值
     * @return true：匹配成功；false：匹配失败
     */
    private boolean comparison(ConditionsDTO conditions, String requestValue, String conditionsValue) {
        // 除了 存在、不存在 外，都直接返回
        if (!(conditions.getComparison() == Comparison.EXISTS) && !(conditions.getComparison() == Comparison.NOT_EXIST)) {
            if (requestValue == null) { // 如果从请求获得的参数值为空，直接匹配失败，跳出循环
                return false;
            }
            if (StringUtils.isEmpty(conditionsValue)) { // 如果期望中只有 key 没有 value，直接匹配成功，跳出循环
                return true;
            }
        }

        switch (conditions.getComparison()) {
            case EQUAL: // 等于
                if (!requestValue.equals(conditionsValue)) {
                    return false;
                }
                break;
            case NOT_EQUAL: // 不等于
                if (requestValue.equals(conditionsValue)) {
                    return false;
                }
                break;
            case IS_ABOVE: // 大于
                long rValue = this.parameterParseLong(requestValue, "请求参数值");
                long cValue = this.parameterParseLong(conditionsValue, "期望参数值");
                if (!(rValue > cValue)) {
                    return false;
                }
                break;
            case IS_ATLEAST: // 大于或等于
                rValue = this.parameterParseLong(requestValue, "请求参数值");
                cValue = this.parameterParseLong(conditionsValue, "期望参数值");
                if (!(rValue >= cValue)) {
                    return false;
                }
                break;
            case IS_BELOW: // 小于
                rValue = this.parameterParseLong(requestValue, "请求参数值");
                cValue = this.parameterParseLong(conditionsValue, "期望参数值");
                if (!(rValue < cValue)) {
                    return false;
                }
                break;
            case IS_AT_MOST: // 小于或等于
                rValue = this.parameterParseLong(requestValue, "请求参数值");
                cValue = this.parameterParseLong(conditionsValue, "期望参数值");
                if (!(rValue <= cValue)) {
                    return false;
                }
                break;
            case INCLUDE: // 包含
                if (!(requestValue.indexOf(conditionsValue) >= 0)) {
                    return false;
                }
                break;
            case NOT_INCLUDE: // 不包含
                if (requestValue.indexOf(conditionsValue) >= 0) {
                    return false;
                }
                break;
            case MATCH: // 正则匹配
                if (!Pattern.matches(conditionsValue, requestValue)) {
                    return false;
                }
                break;
            case EXISTS: // 存在
                if (requestValue == null) {
                    return false;
                }
                break;
            case NOT_EXIST: // 不存在
                if (requestValue != null) {
                    return false;
                }
                break;
            default:
                throw new UnsupportedOperationException("错误的 comparison : " + conditions.getComparison());
        }
        return true;
    }

    /**
     * 将字符串转换为 long
     * @param valueStr 字符串
     * @param errMsg 错误信息
     * @return
     */
    private long parameterParseLong(String valueStr, String errMsg) {
        try {
            return Long.parseLong(valueStr);
        } catch (NumberFormatException e) {
            throw new UnsupportedOperationException(errMsg + " 不是数字，无法比较大小");
        }
    }
}
